public class MembershipRollup {

	public static list<Account> RollupMemberStatusToAccounts ( set<id> AcctsToProcess, set<id> mbrRTids ) {
	
		// this is the simpler case compared to contacts, since we don't need to worry about Households
		// so just take list of accounts, and run history on them
		
		// also take list of membership-type rectype id's (since already looked up by calling trigger)
		
//		set<id> Acctids = new set<id> ();
		list<Account> AcctsUpdated = new List<Account> () ;
		//map<id,list<OpportunityContactRole>> HHtoOpps = new map<id,list<OpportunityContactRole>> ();
		//HouseholdOppHistory[] HHOs = new List<HouseholdOppHistory> ();
		
		map<id,OppHistoryMap> AcctToOpps = new map<id,OppHistoryMap> () ;
		for ( id ThisAcctId : AcctsToProcess ) {
			OppHistoryMap thisOH = new OppHistoryMap (ThisAcctId);
			AcctToOpps.put (ThisAcctId,thisOH);
		}
		
		Opportunity[] MbrChainOpps = [SELECT AccountId, Name, Id, Membership_Start_Date__c, Membership_End_Date__c, Membership_Origin__c, Member_Level__c FROM Opportunity WHERE AccountId IN :AcctsToProcess AND IsWon = true AND RecordTypeId IN :mbrRTids ORDER BY Membership_Start_Date__c ];		
		
		for ( Opportunity thisOpp : MbrChainOpps ) {
			OppHistoryMap thisOH = AcctToOpps.get (thisOpp.AccountId);
			thisOH.AddOpp (thisOpp);	
		}
		
		
		// now loop through all accounts passed in
		// btw, we do want to write to every account, even those w/ no found Opps
		// because we may need to write null values for opps deleted or changed ClosedWon -> prospecting, etc.
		for ( id ThisAcctId : AcctsToProcess ) {

			// need to declare these up here, so they'll be present but null if we have no opps for ann acct			
			date datEnd, datJoin;
			string mbrOrigin, mbrLevel;
			
			OppHistoryMap thisOH = AcctToOpps.get (ThisAcctId);
			Opportunity lastOpp = thisOH.GetLastOpp ();
			Opportunity firstOpp = thisOH.GetFirstOpp ();

			if ( lastOpp != null ) {
				datEnd = lastOpp.Membership_End_Date__c;
				mbrOrigin = lastOpp.Membership_Origin__c;
				mbrLevel = lastOpp.Member_Level__c;
			}
			
			if ( firstOpp != null ) {
				datJoin = firstOpp.Membership_Start_Date__c;
			}
			
				
			Account thisAcct = new Account (id = ThisAcctId);
			//thisAcct.id = ThisAcctId;
			thisAcct.Membership_End_Date__c = datEnd;
			thisAcct.Membership_Join_Date__c = datJoin;
			thisAcct.Membership_Origin__c = mbrOrigin;
			thisAcct.Member_Level__c = mbrLevel;
			
			AcctsUpdated.add (thisAcct);
		}
		
		return AcctsUpdated;
		
		
	}



	public static list<contact> RollupMemberStatusToContacts ( list<id> OppsToProcess , boolean skipOpps, set<id> mbrRTids ) {
		 
		// given a list of Opp id's (which need to be pre-tested that they are memberships
		//   and include con role(s)
		// roll up all membership stats to all contacts in the HH of the primary con role
		//
		// if skipOpps is true, do the same but omit the opp ID's actually passed in when
		//	calculating the stats (meaning figure membership stats based on all OTHER member
		//	opps for the household except the ones passed in)
		//	this is for the before delete trigger: we need to lookup the contacts and HH from
		//		the opp being deleted, but don't want to include that opps in the stats
		//		since it's on the way out
		
		map<id,list<OpportunityContactRole>> HHtoOpps = new map<id,list<OpportunityContactRole>> ();
		map<id,list<Contact>> HHtoCons = new map<id,list<Contact>> ();
		set<id> HHids = new set<id> ();
		set<id> ConIds = new set<id> ();

		// extract list of HH Id's from the opps
		// I think we have to do a separate SELECT here b/c  trigger.new does not include related fields like HHid
		Opportunity[] OppsWithHH = [Select Id, (SELECT ContactID, Contact.ONEN_Household__c FROM OpportunityContactRoles WHERE IsPrimary = True LIMIT 1) From Opportunity WHERE Id IN :OppsToProcess ];
		for ( Opportunity thisOpp : OppsWithHH ) {
				//HHToProcess.put (thisOpp.OpportunityContactRoles[0].Contact.ONEN_Household__c,null);
				id thisHHid = thisOpp.OpportunityContactRoles[0].Contact.ONEN_Household__c;
				if ( ! HHids.contains (thisHHid) ) {
					HHids.add (thisHHid);
				}
		}
		
//		system.debug (HHids);
		
		// query for all of the contacts that are part of these HH's
		// (may be larger more than just the primary contacts from the opps)
		Contact[] cons = [SELECT id, ONEN_Household__c, Membership_End_Date__c, Membership_Join_Date__c, Member_Level__c FROM Contact WHERE ONEN_Household__c IN :HHids];
		for ( Contact thisCon : cons ) {
			
			id thisHHid = thisCon.ONEN_Household__c;
			
			// for each Contact, check to see if we already have an entry in our HH mapping table
			if ( ! HHtoCons.ContainsKey (thisHHid) ) {
				// if not, make a new one
				list<Contact> thisConList = new list<Contact> () ;
				thisConList.add (thisCon);
				HHtoCons.put ( thisHHid , thisConList );
			} else {
				// if so, add to the array of contacts for that HH
				list<Contact> thisConList = HHtoCons.get ( thisHHid );
				thisConList.add (thisCon);
				HHtoCons.put ( thisHHid , thisConList );
			}
		}
		
//		system.debug (HHtoCons);
		
		// get all closedwon membership opps for contacts from those HH's, ordered
		// have to query on OCR's, not Opps, b/c we need to impose the WHERE clause on the OCR
		// if we did a relationship query w/ Opps at the base, we'd get all Opps, along w/ only those OCR's for the selected HH's
		//
		// if skipOpps is true (i.e. we're running a before delete trigger), omit the opps
		// that triggered the delete
		OpportunityContactRole[] MbrChainOCRs = new list<OpportunityContactRole> ();
		if ( skipOpps ) {
			MbrChainOCRs = [SELECT ContactID, Contact.ONEN_Household__c, Opportunity.Name, Opportunity.Id, Opportunity.Membership_Start_Date__c, Opportunity.Membership_End_Date__c, Opportunity.Membership_Origin__c, Opportunity.Member_Level__c FROM OpportunityContactRole WHERE IsPrimary = True  and Contact.ONEN_Household__c IN :HHids and Opportunity.IsWon = true AND Opportunity.RecordTypeId IN :mbrRTids and Opportunity.Id NOT IN :OppsToProcess ORDER BY Opportunity.Membership_Start_Date__c ];
		} else {
			MbrChainOCRs = [SELECT ContactID, Contact.ONEN_Household__c, Opportunity.Name, Opportunity.Id, Opportunity.Membership_Start_Date__c, Opportunity.Membership_End_Date__c, Opportunity.Membership_Origin__c, Opportunity.Member_Level__c FROM OpportunityContactRole WHERE IsPrimary = True  and Contact.ONEN_Household__c IN :HHids and Opportunity.IsWon = true AND Opportunity.RecordTypeId IN :mbrRTids ORDER BY Opportunity.Membership_Start_Date__c ];
		}
		
		if ( MbrChainOCRs.size() > 0 ) {
			//map HH's to opps
			// note: if doing delete, not all HH's/contacts will be represented by any opps here
			for ( OpportunityContactRole thisOCR : MbrChainOCRs ) {
				id thisHHid = thisOCR.Contact.ONEN_Household__c;
				list<OpportunityContactRole> thisOppList = new list<OpportunityContactRole> () ;
				// for each Opp, check to see if we already have an entry in our HH mapping table
				if ( ! HHtoOpps.ContainsKey (thisHHid) ) {
					// if not, make a new one
					thisOppList = new list<OpportunityContactRole> () ;
				} else {
					// if so, add to the array of contacts for that HH (will replace it)
					thisOppList = HHtoOpps.get ( thisHHid );
				}			
				thisOppList.add (thisOCR);
				HHtoOpps.put ( thisHHid , thisOppList );
			}
		}
			
		system.debug ('HHtoOpps: ' + HHtoOpps);
		
		// loop through HH's
		//for ( id thisHHid : HHtoOpps.keyset() ) {
		for ( id thisHHid : HHtoCons.keyset() ) {	
			
			date datEnd, datJoin;
			string mbrOrigin, mbrLevel;
			
			list<OpportunityContactRole> thisOppList = HHtoOpps.get(thisHHid);
			
			if ( thisOppList != null ) {
			
				integer nbrOpps = thisOppList.size();
				
				// TBD figure stats based on list of opps
				// get end date and origin and level from the last opp in the chain
				datEnd = thisOppList[nbrOpps - 1].Opportunity.Membership_End_Date__c;
				mbrOrigin = thisOppList[nbrOpps - 1].Opportunity.Membership_Origin__c;
				mbrLevel = thisOppList[nbrOpps - 1].Opportunity.Member_Level__c;
				
				// for join date, use the first opp in the array, since they were sorted by mbr dates to start w/
				datJoin = thisOppList[0].Opportunity.Membership_Start_Date__c;
			
			} else {
				// do nothing if we have no opps, since everything will already be null
			}
			
			// loop through contacts
			list<Contact> thisConList = HHtoCons.get(thisHHid);
			for ( Contact thisCon : thisConList ) {
				// write each contact obj
				thisCon.Membership_End_Date__c = datEnd;
				thisCon.Membership_Join_Date__c = datJoin;
				thisCon.Membership_Origin__c = mbrOrigin;
				thisCon.Member_Level__c = mbrLevel;
			}
		}

				
		return cons;		
		
	}

}